﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using UtilZ.Dotnet.Ex.Attributes;
using UtilZ.Dotnet.Ex.ValueDescription;

namespace UtilZ.Dotnet.Ex.Communication.Net.Web
{
    /// <summary>
    /// Http响应头
    /// </summary>
    public class HttpResponseHeaders
    {
        private readonly static ValueDescriptionGroup<string> _group = null;
        static HttpResponseHeaders()
        {
            _group = new ValueDescriptionGroup<string>(typeof(HttpResponseHeaders), "Http响应头");
        }

        /// <summary>
        /// 获取http响应头名称
        /// </summary>
        /// <param name="headName">http响应头</param>
        /// <returns>http响应头名称</returns>
        public static string GetStatusCodeName(string headName)
        {
            string name;
            if (_group.TryGetNameByValue(headName, out name))
            {
                return name;
            }
            else
            {
                return $"未知的http状态码\"{headName}\"";
            }
        }

        /// <summary>
        /// 获取http响应头描述
        /// </summary>
        /// <param name="headName">http响应头</param>
        /// <returns>http响应头描述</returns>
        public static string GetStatusCodeDes(string headName)
        {
            string des;
            if (_group.TryGetDescriptionByValue(headName, out des))
            {
                return des;
            }
            else
            {
                return $"未知的http响应头\"{headName}\"";
            }
        }








        /// <summary>
        /// 服务器支持哪些请求方法（如GET、POST等）。
        /// </summary>
        [DisplayNameEx("服务器支持哪些请求方法", "服务器支持哪些请求方法（如GET、POST等）")]
        public const string Allow = "Allow";

        /// <summary>
        /// 内容压缩方法,文档的编码（Encode）方法。只有在解码之后才可以得到Content-Type头指定的内容类型。
        /// 利用gzip压缩文档能够显著地减少HTML文档的下载时间。Java的GZIPOutputStream可以很方便地进行gzip压缩，
        /// 但只有Unix上的Netscape和Windows上的IE 4、IE 5才支持它。因此，Servlet应该通过查看Accept-Encoding头
        /// （即request.getHeader("Accept-Encoding")）检查浏览器是否支持gzip，为支持gzip的浏览器返回经gzip压缩
        /// 的HTML页面，为其他浏览器返回普通页面。
        /// </summary>
        [DisplayNameEx("文档的编码（Encode）方法", "只有在解码之后才可以得到Content-Type头指定的内容类型。利用gzip压缩文档能够显著地减少HTML文档的下载时间。Java的GZIPOutputStream可以很方便地进行gzip压缩，但只有Unix上的Netscape和Windows上的IE 4、IE 5才支持它。因此，Servlet应该通过查看Accept-Encoding头（即request.getHeader(\"Accept - Encoding\")）检查浏览器是否支持gzip，为支持gzip的浏览器返回经gzip压缩的HTML页面，为其他浏览器返回普通页面")]
        public const string ContentEncoding = "Content-Encoding";

        /// <summary>
        /// 表示内容长度。只有当浏览器使用持久HTTP连接时才需要这个数据。如果你想要利用持久连接的优势，
        /// 可以把输出文档写入 ByteArrayOutputStream，完成后查看其大小，然后把该值放入Content-Length头，
        /// 最后通过byteArrayStream.writeTo(response.getOutputStream() 发送内容。        
        /// </summary>
        [DisplayNameEx("表示内容长度", "只有当浏览器使用持久HTTP连接时才需要这个数据。如果你想要利用持久连接的优势，可以把输出文档写入 ByteArrayOutputStream，完成后查看其大小，然后把该值放入Content-Length头，最后通过byteArrayStream.writeTo(response.getOutputStream() 发送内容。")]
        public const string ContentLength = "Content-Length";

        /// <summary>
        /// 分块传输头
        /// </summary>
        [DisplayNameEx("分块传输头", "分块传输头")]
        public const string TransferEncoding = "Transfer-Encoding";


        /// <summary>
        /// 表示后面的文档属于什么MIME类型。Servlet默认为text/plain，但通常需要显式地指定为text/html。
        /// 由于经常要设置Content-Type，因此HttpServletResponse提供了一个专用的方法setContentType。
        /// eg:Content-Type: text/html; charset=utf-8
        /// </summary>
        [DisplayNameEx("表示后面的文档属于什么MIME类型", "Servlet默认为text/plain，但通常需要显式地指定为text/html。由于经常要设置Content-Type，因此HttpServletResponse提供了一个专用的方法setContentType。eg:Content-Type: text/html; charset=utf-8")]
        public const string ContentType = "Content-Type";

        /// <summary>
        /// 当前的GMT时间。你可以用setDateHeader来设置这个头以避免转换时间格式的麻烦。
        /// </summary>
        [DisplayNameEx("当前的GMT时间", "你可以用setDateHeader来设置这个头以避免转换时间格式的麻烦")]
        public const string Date = "Date";

        /// <summary>
        /// 应该在什么时候认为文档已经过期，从而不再缓存它
        /// </summary>
        [DisplayNameEx("过期时间", "应该在什么时候认为文档已经过期，从而不再缓存它")]
        public const string Expires = "Expires";

        /// <summary>
        /// 文档的最后改动时间。客户可以通过If-Modified-Since请求头提供一个日期，该请求将被视为一个条件GET，
        /// 只有改动时间迟于指定时间的文档才会返回，否则返回一个304（Not Modified）状态。Last-Modified也可用setDateHeader方法来设置。
        /// </summary>
        [DisplayNameEx("文档的最后改动时间", "客户可以通过If-Modified-Since请求头提供一个日期，该请求将被视为一个条件GET，只有改动时间迟于指定时间的文档才会返回，否则返回一个304（Not Modified）状态。Last-Modified也可用setDateHeader方法来设置。")]
        public const string LastModified = "Last-Modified";

        /// <summary>
        /// 表示客户应当到哪里去提取文档。Location通常不是直接设置的，而是通过HttpServletResponse的sendRedirect方法，该方法同时设置状态代码为302。
        /// </summary>
        [DisplayNameEx("表示客户应当到哪里去提取文档", "Location通常不是直接设置的，而是通过HttpServletResponse的sendRedirect方法，该方法同时设置状态代码为302")]
        public const string Location = "Location";

        /// <summary>
        /// 表示浏览器应该在多少时间之后刷新文档，以秒计。除了刷新当前文档之外，你还可以通过setHeader("Refresh", "5; URL=http://host/path")让浏览器读取指定的页面。
        /// 注意这种功能通常是通过设置HTML页面HEAD区的＜META HTTP-EQUIV="Refresh" CONTENT="5;URL=http://host/path"＞实现，这是因为，自动刷新或重定向对于那些不能使用CGI或Servlet的HTML编写者十分重要。但是，对于Servlet来说，直接设置Refresh头更加方便。
        /// 注意Refresh的意义是"N秒之后刷新本页面或访问指定页面"，而不是"每隔N秒刷新本页面或访问指定页面"。因此，连续刷新要求每次都发送一个Refresh头，而发送204状态代码则可以阻止浏览器继续刷新，不管是使用Refresh头还是＜META HTTP-EQUIV="Refresh" ...＞。
        /// 注意Refresh头不属于HTTP 1.1正式规范的一部分，而是一个扩展，但Netscape和IE都支持它。
        /// </summary>
        [DisplayNameEx("表示浏览器应该在多少时间之后刷新文档", "表示浏览器应该在多少时间之后刷新文档，以秒计")]
        public const string Refresh = "Refresh";





        /// <summary>
        /// 服务器名字。Servlet一般不设置这个值，而是由Web服务器自己设置。
        /// </summary>
        [DisplayNameEx("服务器名字", "Servlet一般不设置这个值，而是由Web服务器自己设置")]
        public const string Server = "Server";

        /// <summary>
        /// 设置和页面关联的Cookie。Servlet不应使用response.setHeader("Set-Cookie", ...)，而是应使用HttpServletResponse
        /// 提供的专用方法addCookie。参见下文有关Cookie设置的讨论。
        /// </summary>
        [DisplayNameEx("设置和页面关联的Cookie", "Servlet不应使用response.setHeader(\"Set - Cookie\", ...)，而是应使用HttpServletResponse提供的专用方法addCookie。参见下文有关Cookie设置的讨论。")]
        public const string SetCookie = "Set-Cookie";

        /// <summary>
        /// 客户应该在Authorization头中提供什么类型的授权信息？在包含401（Unauthorized）状态行的应答中这个头是必需的。
        /// 例如，response.setHeader("WWW-Authenticate", "BASIC realm=＼"executives＼"")。注意Servlet一般不进行这方面的处理，
        /// 而是让Web服务器的专门机制来控制受密码保护页面的访问（例如.htaccess）。
        /// </summary>
        [DisplayNameEx("客户应该在Authorization头中提供什么类型的授权信息", "在包含401（Unauthorized）状态行的应答中这个头是必需的。例如，response.setHeader(\"WWW - Authenticate\", \"BASIC realm =\"executives\")。注意Servlet一般不进行这方面的处理，而是让Web服务器的专门机制来控制受密码保护页面的访问（例如.htaccess）")]
        public const string WWWAuthenticate = "WWW-Authenticate";




        /// <summary>
        /// 响应头中有此项表示支持范围请求
        /// </summary>
        [DisplayNameEx("支持请求范围", "响应头中有此项表示支持范围请求")]
        public const string AcceptRanges = "Accept-Ranges";

        /// <summary>
        /// 响应客户端数据范围
        /// </summary>
        [DisplayNameEx("响应客户端数据范围", "响应客户端数据范围")]
        public const string ContentRange = "Content-Range";





        /// <summary>
        /// 同意升级Websocket
        /// </summary>
        [DisplayNameEx("同意升级Websocket", "同意升级Websocket")]
        public const string Upgrade = "Upgrade";

        /// <summary>
        /// 验证通过响应头
        /// </summary>
        [DisplayNameEx("验证通过响应头")]
        public const string SecWebSocketAccept = "Sec-WebSocket-Accept";

        /// <summary>
        /// Connection，指定特定连接需要的选项。
        /// </summary>
        [DisplayNameEx("指定特定连接需要的选项")]
        public const string Connection = "Connection";




        /// <summary>
        /// 跨域访问。对于不需具备凭证（credentials）的请求，服务器会以“*”作为通配符，从而允许所有域都具有访问资源的权限
        /// </summary>
        [DisplayNameEx("跨域访问", "对于不需具备凭证（credentials）的请求，服务器会以“*”作为通配符，从而允许所有域都具有访问资源的权限")]
        public const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";

        /// <summary>
        /// 跨域访问。该字段是必须的，用来列出浏览器的CORS请求会用到哪些HTTP方法
        /// </summary>
        [DisplayNameEx("列出浏览器的CORS请求会用到哪些HTTP方法", "跨域访问。该字段是必须的，用来列出浏览器的CORS请求会用到哪些HTTP方法")]
        public const string AccessControlAllowMethods = "Access-Control-Allow-Methods";

        /// <summary>
        /// 跨域访问。该字段是一个逗号分隔的字符串，指定浏览器CORS请求会额外发送的头信息字段
        /// </summary>
        [DisplayNameEx("指定浏览器CORS请求会额外发送的头信息字段", "跨域访问。该字段是一个逗号分隔的字符串，指定浏览器CORS请求会额外发送的头信息字段")]
        public const string AccessControlAllowHeaders = "Access-Control-Allow-Headers";


        /*
         * 如果Origin指定的域名在许可范围内，服务器返回的响应，会多出几个头信息字段。都以Access-Control- 开头：
         * 
        （1）Access-Control-Allow-Origin

该字段是必须的。它的值要么是请求时Origin字段的值，要么是一个*，表示接受任意域名的请求。

需要注意的是，如果要发送Cookie，Access-Control-Allow-Origin就不能设为星号，必须指定明确的、与请求网页一致的域名。同时，Cookie依然遵循同源政策，只有用服务器域名设置的Cookie才会上传，其他域名的Cookie并不会上传，且（跨源）原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

（2）Access-Control-Allow-Credentials

该字段可选。它的值是一个布尔值，表示是否允许发送Cookie。默认情况下，Cookie不包括在CORS请求之中。设为true，即表示服务器明确许可，Cookie可以包含在请求中，一起发给服务器。这个值也只能设为true，如果服务器不要浏览器发送Cookie，删除该字段即可。

（3）Access-Control-Expose-Headers

该字段可选。CORS请求时，XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段：Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段，就必须在Access-Control-Expose-Headers里面指定。
        */
    }
}
